#include "mpi.h"
#include <stdio.h>
#include "stdafx.h"
#include "Solver.h"
#include "Settings.h"

Solver :: Solver(Configurator* configurator, int argc, char** argv)
{
	MPI_Init(&argc, &argv);
	MPI_Comm_size(MPI_COMM_WORLD, &commsize);
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
	//fix: C4101
	//MPI_Status status;

	threadBounds = (int*) malloc (sizeof(int)*(commsize + 1));
	if(rank == root)
	{
		dwarfConfigurator = configurator;					// process settings from the command line 
		dwarfConfigurator -> WriteSettings();
		dwarfConfigurator -> GetContent(&mainContent, &contentSize);		InitParallelization(commsize);								// content boards define
	}

	MPI_Bcast(
		&contentSize,
		1,
		MPI_INT,
		root,
		MPI_COMM_WORLD);

	MPI_Bcast(
		threadBounds,
		commsize + 1,
		MPI_INT,
		root,
		MPI_COMM_WORLD);
	
	if(rank != root)
	{
		mainContent = (char*) malloc (sizeof(char)*(contentSize));
	}

	MPI_Bcast(
		mainContent, 
		contentSize,
		MPI_CHAR,
		root,
		MPI_COMM_WORLD);
	MPI_Barrier(MPI_COMM_WORLD);								// waiting for the end getting data by root
}

void Solver::InitParallelization(int threadCount)
{
    int interval = 1 + contentSize / threadCount;   // partitioning the String content (same parts for all threads)
    int leftBoard = 0;
    int part = 0;									// iteration counter for parts of content
	threadBounds[0] = 0;
	threadBounds[threadCount] = contentSize;
    while(part < threadCount - 1)
    {
		int certainInterval = interval;
        while (mainContent[leftBoard + certainInterval] != ' ')	// search of middle-word spaces
        {
			certainInterval++;
        }
		certainInterval++;
        leftBoard += certainInterval;						// interval bounds defifition
		threadBounds[part + 1] = leftBoard;
		part++;
    }
}

void Solver::Solve()
{
	Map();						// collect the word entries into the StringEntries list
	Reduce();					// count the number of entry of each word into the StringTotal map
	if (rank == root)			// the root process must collect the result Maps of all processors
	{
		MPI_Status status;
		for (int i = 1; i < commsize; i++)		// request for maps receiving from all non-root processors
		{
			int recCount;						// receive count of <key, value> pairs
			MPI_Recv(&recCount, 1, MPI_INT, i, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
			for (int j = 0; j < recCount; j++)
			{
				char* newKey;
				int newValue = 0;
				int newKeyLength = 0;			// receive all <key, value> pairs

				MPI_Recv(&newKeyLength, 1, MPI_INT, i, MPI_ANY_TAG, MPI_COMM_WORLD, &status);

				newKey = (char*) malloc (sizeof(char)*(newKeyLength));
				MPI_Recv(newKey, newKeyLength, MPI_CHAR, i, MPI_ANY_TAG, MPI_COMM_WORLD, &status);

				MPI_Recv(&newValue, 1, MPI_INT, i, MPI_ANY_TAG, MPI_COMM_WORLD, &status);

				string newString(reinterpret_cast<const char*>(newKey), newKeyLength);	// convert from *char to string
				for (map<string, int>::const_iterator it = stringTotal.begin(); it != stringTotal.end(); ++it)
				{
					if ((it -> first)==newString)			// needs to be refreshed 
					{
						newValue += it -> second;			// updating Dictionary values
						break;								// entry finded
					}
				}
				stringTotal[newString] = newValue;			// refresh
			}
		}
	}
	else			// non-root processes must send own results to the root
	{
		int size = (int)stringTotal.size();
		MPI_Send(&size, 1, MPI_INT, root, root, MPI_COMM_WORLD);	// send the Map (Dictionary) size
		for (map<string, int>::const_iterator it = stringTotal.begin(); it != stringTotal.end(); ++it)
		{												// traverse all the map
			string key = it -> first;					// get all the keys
			int currentKeyLength = key.size();			// get key sizes for sending
			int value = it -> second;					// get values

			char *word = (char*) malloc (sizeof(char)*(currentKeyLength));
			strcpy(word, key.c_str());
			MPI_Send(&currentKeyLength, 1, MPI_INT, root, root, MPI_COMM_WORLD);	// send key length

			MPI_Send(word, currentKeyLength, MPI_CHAR, root, root, MPI_COMM_WORLD);	// send key
			
			MPI_Send(&value, 1, MPI_INT, root, root, MPI_COMM_WORLD);				// send value
		}
		stringTotal.clear();				// clearing the unused memory (all data is already sent)
	}
}

string Solver::TransformCharFragment(int begin, int end)
{
	int length = end - begin;
	char* currentWord = (char*) malloc (sizeof(char)*length);				// dynamic memory allocation for new char array
	for (int j = 0; j < length; j++)
	{
		currentWord[j] = mainContent[begin + j];				     		// filling the array
	}
	string transform(reinterpret_cast<const char*>(currentWord), length);	// convert to string
	free(currentWord);														// free memory from char array
	return transform;
}

// Traverses the string 'content'. Collect each word as substring of 'content'. Change the 'stringEntries' value
void Solver::Map()
{
	int i = threadBounds[rank];											// iteration counter
    int count = 0;						// local variable for storing the length of each word
	while (i < threadBounds[rank + 1])
	{
		if ((mainContent[i] > 47 && mainContent[i] < 58)  ||
            (mainContent[i] > 96 && mainContent[i] < 123) ||
            (mainContent[i] > 64 && mainContent[i] < 91)  ||
             mainContent[i] == 95						  ||
             mainContent[i] == 45					      ||
             mainContent[i] == 39)
		{
			count++;														// getting the length of a string
        }
        else
        {
			if (count > 0)
			{
				string stringFragment = TransformCharFragment(i - count, i);
				stringEntries.push_back(stringFragment);					// add substring
            }
			string stringFragment = TransformCharFragment(i, i + 1);
			stringEntries.push_back(stringFragment);						// add single-char string
            count = 0;														// start length counting of a new word
        }
        i++;
    }
	free(mainContent);		// clearing the unused memory (all data is already consisted in stringEntries)
} 

//Groups 'stringEntries' elements and count the number of entry of each word
void Solver::Reduce()
{
	stringEntries.sort();
    int count = 1;										// local variable for storing the count of each word
	string strCur;
	string strPre = stringEntries.front();
	stringEntries.pop_front();	
	while (!stringEntries.empty())						// traverse the list	
    {
		strCur = stringEntries.front();
		stringEntries.pop_front();										
		if (strCur == strPre)							// checking the equivalence of adjoining words
		{
			count++;                                    // getting count of the word
        }
		else
        {
			stringTotal[strPre] = count;				// add to the final list
            count = 1;									// start the next word's count
        }
		strPre = strCur;
    }
	stringTotal[strPre] = count;				// add to the final list
	stringEntries.clear();			// clearing the unused memory (all entrines is already consisted in stringTotal)
}

void Solver::Finish(double time)	// Problem results output
{
	if(rank == root)
	{
		dwarfConfigurator -> Close(time, stringTotal);
		stringTotal.clear();			// clearing the unused memory (all data is already printed)
	}
	MPI_Finalize();
}